package com.hhf.ftp.service.impl;

import com.hhf.ftp.service.BaseConst;
import com.hhf.ftp.service.FtpService;
import net.schmizz.sshj.common.IOUtils;
import org.apache.commons.net.ftp.FTP;
import org.apache.commons.net.ftp.FTPClient;
import org.apache.commons.net.ftp.FTPFile;
import org.apache.commons.net.ftp.FTPReply;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import java.io.*;
import java.util.StringTokenizer;

@Service("ftp")
public class FtpServiceImpl implements FtpService {


    private final static String HOME_DIR = System.getProperty("user.home");
    private static String LOCAL_ENCODING = System.getProperty("sun.jnu.encoding");

    protected FTPClient client;

    @Override
    public String connection(String username, String password, String ip, Integer port) throws IOException {
        if(port == null) {
            port = 21;
        }
        getClient(username, password, ip, port);
        return "FTP connection success!";
    }

    /**
     * 上传文件，指定文件名编码
     * @param directory
     * @param uploadFile
     * @throws IOException
     */
    @Override
    public void put(String uploadFile, String directory, String encode) throws IOException {
        encode = StringUtils.hasText(encode) ? encode : LOCAL_ENCODING;
        mkdir(directory,encode);
        File file = new File(uploadFile);
        InputStream is = new FileInputStream(file);
        client.storeFile(new String(file.getName().getBytes(encode), client.getControlEncoding()), is);
        IOUtils.closeQuietly(is);
    }

    @Override
    public void get(String file, String localPath, String encode) throws IOException {
        encode = StringUtils.hasText(encode) ? encode : LOCAL_ENCODING;
        int endIndex = file.lastIndexOf("/")+1;
        String filePath = file.substring(0, endIndex);
        file = file.substring(endIndex);
        if(StringUtils.hasText(filePath)) {
            cd(filePath, encode);
        }
        if (!StringUtils.hasText(localPath)) {
            localPath = HOME_DIR;
        }
        File localFile = new File(localPath + File.separatorChar + file);
        OutputStream os = new FileOutputStream(localFile);

        boolean b = client.retrieveFile(new String(file.getBytes(encode),client.getControlEncoding()), os);
        if(!b) {
           throw new RuntimeException("文件下载失败，可能是文件的文件名编码问题，请传参指定编码");
        }
        IOUtils.closeQuietly(os);
    }

    @Override
    public void rm(String filePath, String encode) throws IOException {
        encode = StringUtils.hasText(encode) ? encode : LOCAL_ENCODING;
        filePath = new String(filePath.getBytes(encode), client.getControlEncoding());
        FTPFile[] listFiles = client.listFiles(filePath);
        if (isFile(listFiles)) {
            client.deleteFile(filePath);
            return;
        }
        client.changeWorkingDirectory(filePath);
        for (FTPFile ftpFile : listFiles) {
            rmFile(ftpFile);
        }
        if (!filePath.endsWith("/")) {
            client.changeToParentDirectory();
            client.removeDirectory(filePath);
        }
    }

    @Override
    public void cd(String directory, String encode) throws IOException {
        encode = StringUtils.hasText(encode) ? encode : LOCAL_ENCODING;
        String path = new String(directory.getBytes(encode),client.getControlEncoding());
        client.changeWorkingDirectory(path);
    }

    @Override
    public void cdup() throws IOException {
        client.changeToParentDirectory();
    }

    /**
     * 创建多级目录，（有则切换目录，无则创建目录）
     * @param directory
     * @throws IOException
     */
    @Override
    public void mkdir(String directory, String encode) throws IOException {
        encode = StringUtils.hasText(encode) ? encode : LOCAL_ENCODING;
        directory = new String(directory.getBytes(encode), client.getControlEncoding());
        StringTokenizer s = new StringTokenizer(directory, "/");
        s.countTokens();
        String dir = "";
        while (s.hasMoreElements()) {
            dir = dir + "/" + (String) s.nextElement();
            client.makeDirectory(dir);
        }
    }

    @Override
    public String ls(String dir, String encode) throws IOException {
        dir = StringUtils.hasText(dir)? dir : client.printWorkingDirectory();
        encode = StringUtils.hasText(encode) ? encode : LOCAL_ENCODING;
        FTPFile[] fileArray = client.listFiles(dir);
        String files = "";
        for(int i=0; i<fileArray.length; i++) {
            FTPFile file = fileArray[i];
            String fileStr = file.toFormattedString();
            files += new String(fileStr.getBytes(client.getControlEncoding()), encode)+"\n";
        }
        return files;
    }

    @Override
    public String pwd(String encode) throws IOException {
        encode = StringUtils.hasText(encode) ? encode : LOCAL_ENCODING;
        String result = client.printWorkingDirectory();
        return new String(result.getBytes(client.getControlEncoding()), encode);
    }

    @Override
    public void mv(String from, String to, String encode) throws IOException {
        encode = StringUtils.hasText(encode) ? encode : LOCAL_ENCODING;
        boolean result = client.rename(new String(from.getBytes(encode), client.getControlEncoding()),
                new String(to.getBytes(encode), client.getControlEncoding()));
        if(!result) {
            throw new RuntimeException("移动/重命名文件失败");
        }
    }

    @Override
    public String bash(String cmdLine) throws IOException {
        return client.sendCommand(cmdLine)+"";
    }

    @Override
    public void scpPut(String uploadFile, String directory, String encode) throws IOException {
        put(uploadFile, directory, encode);
    }

    @Override
    public void scpGet(String filename, String localPath, String encode) throws IOException {
        get(filename, localPath, encode);
    }

    @Override
    public String close(){
        if (client != null){
            try {
                client.logout();
                client.disconnect();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return "Bye!";
    }

    /**
     * 将当前传输模式改为本地被动模式，被动模式需要服务端开放随机端口(1024以上)给客户端.
     * */
    public void enterPASVMode() {
        this.client.enterLocalPassiveMode();
    }

    /**
     * 将当前传输模式改为本地主动模式，主动模式需要客户端开放随机端口(1024以上)给服务器.
     * */
    public void enterActiveMode() {
        this.client.enterLocalActiveMode();
    }

    private void getClient(String username, String password, String ip, Integer port) throws IOException {
        FTPClient ftpClient = new FTPClient();
        ftpClient.connect(ip, port);// 连接FTP服务器
        int reply = ftpClient.getReplyCode();
        if(FTPReply.isPositiveCompletion(reply)){
            if(ftpClient.login(username, password)){
                ftpClient.enterLocalPassiveMode();
                ftpClient.setFileType(FTP.BINARY_FILE_TYPE);
                ftpClient.setRemoteVerificationEnabled(false);
                ftpClient.setDataTimeout(1000 * 7200);
                ftpClient.setConnectTimeout(1000 * 1800);
            }
        }
        if (FTPReply.isPositiveCompletion(ftpClient.sendCommand("OPTS UTF8", "ON"))) {
            LOCAL_ENCODING = BaseConst.UTF_8;
        }
        this.client = ftpClient;
    }

    private static void ioCopy(InputStream is, OutputStream os) throws IOException {
        byte[] bytes = new byte[100];
        int b = 0;
        while ((b = is.read(bytes)) != -1) {
            os.write(bytes, 0, b);
        }
    }

    private boolean isFile(FTPFile[] listFiles) {
        return listFiles!= null && listFiles.length == 1 && listFiles[0].isFile();
    }

    /**
     * 递归删除文件或文件夹
     * @param file
     * @throws IOException
     */
    private void rmFile(FTPFile file) throws IOException {
        if(file.isDirectory()) {
            if (".".equals(file.getName()) || "..".equals(file.getName())) {
                return;
            }
            cd(file.getName(),null);
            // 删除空目录
            FTPFile[] listFiles = client.listFiles();
            if(dirEmpty(listFiles)) {
                String pathname = pwd(null);
                cdup();
                client.removeDirectory(pathname);
                return;
            }
            for (FTPFile ftpFile : listFiles) {
                rmFile(ftpFile);
            }
            //删除空目录
            FTPFile[] fList = client.listFiles();
            if (dirEmpty(fList)) {
                String pathname = pwd(null);
                client.removeDirectory(pathname);
            }
            client.changeToParentDirectory();
        } else if(file.isFile()) {
            client.deleteFile(pwd(null) + "/" + file.getName());
        } else {
            return;
        }
    }

    private boolean dirEmpty (FTPFile[] listFiles) {
        return listFiles == null
                || listFiles.length == 0
                || (listFiles.length == 2 && ".".equals(listFiles[0].getName())
                && "..".equals(listFiles[1].getName()))
                || (listFiles.length == 2 && "..".equals(listFiles[0].getName())
                && ".".equals(listFiles[1].getName()));
    }
}
